<?php

declare (strict_types = 1);

namespace app\helper;

use app\helper\CommonHelper;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\ConnectException;
use support\Log;

final class FastRequest
{
    /**
     * 获取站点种子数据
     * @param mixed $site
     * @param array $pieces_hash
     * [
     *   'pieces_hash_1' => 'pieces_hash_1',
     * ]
     * @return array
     * [
     *   'pieces_hash' => [download_url]
     * ]
     */
    public static function pieces_request($site, array $pieces_hash): array
    {
        $adapter = self::get_adapter($site->site_url);

        $data = [
            'api_url' => $site->api_url,
            'passkey' => $site->passkey,
            'pieces_hash' => $pieces_hash,
        ];
        $pieces_hash_torrent_id = self::guzzle_request(self::safe_get_value($adapter, 'pieces'), $data);

        if (count($pieces_hash_torrent_id) == 0) {
            return [];
        }

        // 种子库插入数据事件
        // event('torrent_add', [
        //     'site_url' => $site['site_url'],
        //     'torrents' => $pieces_hash_torrent_id,
        // ]);

        return self::get_download_url($site, $pieces_hash_torrent_id);
    }

    /**
     * 获取下载地址
     * @param mixed $site
     * @param array $pieces_hash_torrent_id
     * 常规如下，具体看app\config\site.php的配置情况
     * [
     *   {pieces_hash}  => {torrent_id},
     * ];
     * @return array
     */
    private static function get_download_url($site, array $pieces_hash_torrent_id): array
    {
        $adapter = self::get_adapter($site['site_url']);

        // 模板需要的数据
        $data = [
            'site_url' => $site['site_url'],
            'passkey' => $site['passkey'],
        ];

        $download_url = [];
        foreach ($pieces_hash_torrent_id as $key => $value) {
            $data['torrent_id'] = $value;
            $download_url[$key] = self::template_parse($adapter['download'], $data);
        }

        return $download_url;
    }

    /**
     * 封装的一个通用guzzle请求
     * @param array $config
     * @param array $data
     * [
     *   'api_url'      =>  'api_url',
     *   'passkey'      =>  'passkey',
     *   'pieces_hash'  =>  'pieces_hash'
     * ]
     * @return array
     */
    private static function guzzle_request(array $config, array $data): array
    {
        $url = $data['api_url'];
        $request_url = self::template_parse(self::safe_get_value($config, 'request.url'), $data);
        $method = self::safe_get_value($config, 'request.method');
        $type = self::safe_get_value($config, 'request.type');

        $client = new Client();
        $parsedUrl = parse_url($url);
        $host = $parsedUrl['host'];
        $headers = array(
            'Content-Type: application/json',
            'Accept: */*',
            'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36',
            'Referer:' . $url,
            'Host:' . $host,
        );

        try {
            $options = [
                'headers' => $headers,
                'timeout' => 30,
                'verify' => false,
            ];

            if ($type == 'JSON') {
                $options['json'] = self::array_template_parse(self::safe_get_value($config, 'request.data'), $data);
            }

            $response = $client->request($method, $request_url, $options);
        } catch (ClientException $e) {
            $msg = '站点访问失败,链接:' . $url;
            Log::error($msg . $e->getMessage());
            return [];
            // throw new KnownException($msg);
        } catch (ConnectException $e) {
            $msg = '站点访问超过30秒,链接:' . $url;
            Log::error($msg . $e->getMessage());
            return [];
            // throw new KnownException($msg);
        } catch (\Exception $e) {
            $msg = '未处理的异常' . $url;
            $errormsg = $e->getMessage();
            Log::error($msg . $errormsg);
            return [];
            // throw new KnownException($msg . substr($errormsg, 0, 50));
        }

        $statusCode = $response->getStatusCode();
        $content = (string) $response->getBody();
        $response->getBody()->close();

        if ($statusCode != 200) {
            $msg = '状态码:' . $statusCode . ',返回:' . substr($content, 0, 20) . $url;
            return [];
            // throw new KnownException($msg);
        }

        $response_json = json_decode($content, true);
        // if (!is_array($response_json)) {
        //     $msg = '返回格式非json,返回:' . substr($content, 0, 50) . $url;
        //     // 说明不支持，该阻断就阻断，不要一直请求
        //     throw new \Exception($msg);
        // }

        $data_key = self::safe_get_value($config, 'response.data');
        if (!isset($response_json[$data_key])) {
            $msg = '返回格式与预期不一致,返回:' . substr($content, 0, 50) . $url;
            // 说明不支持，该阻断就阻断，不要一直请求
            // throw new \Exception($msg);
            return [];
        }

        /**
         * 正常应该就是以下格式，有问题再兼容
         * [
         *   {pieces_hash}  => {torrent_id},
         * ];
         */
        return $response_json[$data_key];
    }

    /**
     * 获取站点的适配器
     * @param string $site_url
     * @return array
     */
    private static function get_adapter(string $site_url): array
    {
        $sites = config('site.sites', []);
        $site_url = CommonHelper::site_url_standard($site_url);

        // $adapter = isset($sites[$site_url]) ?
        //          Config::get('site.adapter.' . $sites[$site_url]):
        //          Config::get('site.adapter.np');
        $adapter = config('site.adapter.np');
        if (!$adapter) {
            throw new \ErrorException('配置缺失' . $site_url);
        }

        return $adapter;
    }

    /**
     * 将数组模板的值替换为实际数据
     * @param array $template
     * @param array $data
     * @return array
     */
    private static function array_template_parse(array $template, array $data): array
    {
        foreach ($template as $key => $value) {
            $template[$key] = self::template_parse($value, $data);
        }

        return $template;
    }

    /**
     * 模板替换为实际数据
     * $data暂只支持一维数组，目前够了懒的搞
     * @param string $template
     * @param array $data
     * @return mixed
     */
    private static function template_parse(string $template, array $data)
    {
        preg_match_all('/{(.*?)}/', $template, $matches);

        $keys = $matches[1];
        foreach ($keys as $key) {
            if (!isset($data[$key])) {
                throw new \InvalidArgumentException('缺少参数：' . $data[$key]);
            }
            if (is_array($data[$key])) {
                return $data[$key];
            }
            $template = str_replace('{' . $key . '}', (string) $data[$key], $template);
        }

        return $template;
    }

    /**
     * 从多纬数组中读取对应的值
     * @param array $config
     * @param string $name
     * @param mixed $default
     * @return mixed
     */
    private static function safe_get_value(array $config, string $name, $default = null)
    {
        if (empty($name)) {
            return $default;
        }

        $name = explode('.', $name);

        // 按.拆分成多维数组进行判断
        foreach ($name as $val) {
            if (isset($config[$val])) {
                $config = $config[$val];
            } else {
                return $default;
            }
        }

        return $config;
    }
}
